-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[libc++] Make <map>
std::map
constexpr as part of P3372R3
#134330
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
[libc++] Make <map>
std::map
constexpr as part of P3372R3
#134330
Conversation
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
46d0bb9
to
9ea718f
Compare
@frederick-vs-ja can you reopen this when you get a chance? I don't see a reopen button I think it keeps getting closed because I linked a PR review comment in the previous PR, and unfortunately GitHub thinks that implies the PR should also be closed. (And is there a way to explicitly disable that feature for one PR?) |
…P3372-constexpr-map
…P3372-constexpr-map
…P3372-constexpr-map
Can you take a look at this pull request when you have a chance? Thank you |
bump |
libcxx/docs/ReleaseNotes/22.rst
Outdated
by up to 2.5x | ||
- The performance of ``map::insert_or_assign`` has been improved by up to 2x | ||
- ``ofstream::write`` has been optimized to pass through large strings to system calls directly instead of copying them | ||
- P3372R3: ``constexpr map``(`Github <https://github.com/llvm/llvm-project/issues/128660>`__) (The paper is partially implemented. ``constexpr map`` is implemented in this release) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please put this in the Implemented Papers
section (and update the URL).
libcxx/include/__node_handle
Outdated
/* | ||
As this method this->key() returns a non-const reference | ||
to the `key_type` which is const inside `node_type`. | ||
This means that we can't make this member method constexpr until CWG2514 is resolved. | ||
This disallows making the member function constexpr. | ||
More here: | ||
* https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3372r3.html#node-handle-key | ||
* https://cplusplus.github.io/CWG/issues/2514.html | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We really don't need a long explanation for why the standard decided to do certain things. A not that it's not constexpr
according to the standard seems good enough to me.
Also, we don't use C-style comments usually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in cdac01f
libcxx/include/__tree
Outdated
// while preserving in-order order. | ||
template <class _NodePtr> | ||
_LIBCPP_HIDE_FROM_ABI void __tree_right_rotate(_NodePtr __x) _NOEXCEPT { | ||
_LIBCPP_CONSTEXPR_SINCE_CXX26 _LIBCPP_HIDE_FROM_ABI void __tree_right_rotate(_NodePtr __x) _NOEXCEPT { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please stay consistent with the ordering.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in d1dfb7f
pointer __left_; | ||
|
||
_LIBCPP_HIDE_FROM_ABI __tree_end_node() _NOEXCEPT : __left_() {} | ||
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __tree_end_node() _NOEXCEPT : __left_(nullptr) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __tree_end_node() _NOEXCEPT : __left_(nullptr) {} | |
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __tree_end_node() _NOEXCEPT : __left_() {} |
Unrelated change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this may have been an attempt to try to fix the G++-15 error / arm CI error. Can be reverted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 0e10680
This is actually a needed change.
if I revert it to __left_()
, I get the following test failure:
# | /llvm/libcxx/test/std/containers/associative/map/map.access/at.pass.cpp:185:17: error: static assertion expression is not an integral constant expression
# | 185 | static_assert(test());
# | | ^~~~~~
# | /llvm/libcxx/test/support/min_allocator.h:224:64: note: read of uninitialized object is not allowed in a constant expression
# | 224 | TEST_CONSTEXPR_CXX14 explicit operator bool() const { return ptr_ != nullptr; }
# | | ^~~~
# | /llvm/build/myclang22/libcxx/test-suite-install/include/c++/v1/__memory/pointer_traits.h:263:10: note: in call to '__p.operator bool()'
# | 263 | return __p ? __ptr_traits::pointer_to(*static_cast<__element_type*>(std::addressof(*__p)))
# | | ^~~
# | /llvm/build/myclang22/libcxx/test-suite-install/include/c++/v1/__tree:915:12: note: in call to '__static_fancy_pointer_cast<min_pointer<std::__tree_node<std::__value_type<int, double>, min_pointer<void>>>, min_pointer<std::__tree_node_base<min_pointer<void>>>>(m.__tree_.struct (anonymous struct at /llvm/build/myclang22/libcxx/test-suite-install/include/c++/v1/__tree:883:3).__end_node_.__left_)'
# | 915 | return std::__static_fancy_pointer_cast<__node_pointer>(__end_node()->__left_);
# | | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# | /llvm/build/myclang22/libcxx/test-suite-install/include/c++/v1/__tree:1130:9: note: in call to 'this->__root()'
# | 1130 | if (__root() == nullptr) {
# | | ^~~~~~~~
# | /llvm/build/myclang22/libcxx/test-suite-install/include/c++/v1/map:1150:5: note: in call to 'this->__tree_.__insert_range_unique<std::pair<const int, double> *, std::pair<const int, double> *>(&ar[0], &ar[7])'
# | 1150 | __tree_.__insert_range_unique(__first, __last);
# | | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# | /llvm/build/myclang22/libcxx/test-suite-install/include/c++/v1/map:969:5: note: in call to 'this->insert<std::pair<const int, double> *>(&ar[0], &ar[7])'
# | 969 | insert(__f, __l);
# | | ^~~~~~~~~~~~~~~~
# | /llvm/libcxx/test/std/containers/associative/map/map.access/at.pass.cpp:113:61: note: in call to 'map<std::pair<const int, double> *>(&ar[0], &ar[7], key_compare())'
# | 113 | std::map<int, double, std::less<int>, min_allocator<V>> m(ar, ar + sizeof(ar) / sizeof(ar[0]));
# | | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# | /llvm/libcxx/test/std/containers/associative/map/map.access/at.pass.cpp:185:17: note: in call to 'test()'
# | 185 | static_assert(test());
libcxx/include/__tree
Outdated
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __tree_node_base() = default; | ||
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 ~__tree_node_base() = default; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, unrelated change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in b556c8426391
libcxx/include/__tree
Outdated
public: | ||
_LIBCPP_HIDE_FROM_ABI __node_value_type& __get_value() { return *reinterpret_cast<__node_value_type*>(__buffer_); } | ||
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __node_value_type& __get_value() { | ||
return *reinterpret_cast<__node_value_type*>(__buffer_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either this reinterpret_cast
is not needed or the constexpr
annotation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This __get_value()
should be left unchanged. It's only used in C++03 mode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 8ecbbb5ac277
// TODO: workaround for arm CI which uses clang-19.1.4 | ||
/* | ||
note: non-literal type 'std::__tree_node<std::__value_type<int, int>, void *>' cannot be used in a constant expression | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just wait for the bots to be upgraded? It's currently in-progress.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to know! Is there a github issue/pull request tracking that change?
libcxx/include/__tree
Outdated
_LIBCPP_HIDE_FROM_ABI __tree_node_destructor(const __tree_node_destructor&) = default; | ||
__tree_node_destructor& operator=(const __tree_node_destructor&) = delete; | ||
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __tree_node_destructor(const __tree_node_destructor&) = default; | ||
_LIBCPP_CONSTEXPR_SINCE_CXX26 __tree_node_destructor& operator=(const __tree_node_destructor&) = delete; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
= delete
d members don't need annotations.
libcxx/include/__tree
Outdated
__node_allocator& __na = __node_alloc(); | ||
__node_traits::destroy(__na, std::addressof(__lhs)); | ||
|
||
using __node_value_type = __get_node_value_type_t<value_type>; | ||
__node_value_type __tmp(__rhs.first, __rhs.second); | ||
|
||
__node_traits::construct(__na, std::addressof(__lhs), std::move(__tmp)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a significant performance regression as-is. We should only do this during constant evaluation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c28aac0 and c448834 solve this, but increase the "complexity" of the code. I've added comments on why that's needed for now, but can be removed as needed.
My understanding so far:
-
if we use
if constexpr
, this means for all types that satisfy thecopy-constructible
trait, the code will always prefer copying. Types that don't satisfy this trait, will fail to compile during constant evaluation -
if (std::is_constant_evaluated()
allows us to "diverge" the runtime/compile time code paths, which means removing theconstexpr
otherwise the compiler yells sayingif constexpr(std::is_constant_evaluated() && other)
is always true. -
However, that change means any code within
if
must compile (instantiate valid template code), which is not true when dealing with non-copy-constructible types. -
Hence, to delay the template instantiation, and allow compiling (hence, defer to the original runtime code path with UB), I defined a new overload that "overloads" on the
true_type
/false_type
argument, similar to:llvm-project/libcxx/include/__tree
Lines 1223 to 1232 in 0aa7da0
_LIBCPP_HIDE_FROM_ABI void __copy_assign_alloc(const __tree& __t) { __copy_assign_alloc(__t, integral_constant<bool, __node_traits::propagate_on_container_copy_assignment::value>()); } _LIBCPP_HIDE_FROM_ABI void __copy_assign_alloc(const __tree& __t, true_type) { if (__node_alloc() != __t.__node_alloc()) clear(); __node_alloc() = __t.__node_alloc(); } _LIBCPP_HIDE_FROM_ABI void __copy_assign_alloc(const __tree&, false_type) {}
libcxx/include/__tree
Outdated
__insert_unique_from_orphaned_node(const_iterator __p, __get_node_value_type_t<_Tp>&& __value) { | ||
__emplace_hint_unique(__p, const_cast<key_type&&>(__value.first), std::move(__value.second)); | ||
#if _LIBCPP_STD_VER >= 26 | ||
if constexpr (integral_constant<bool, is_copy_constructible<decltype(__value.first)>::value >()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we just use if constexpr(is_copy_constructible_v<...>
? Also, let's only use this branch in constant evaluation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simplified here: 2661937
for the "only in constant evaluation part, I've had to change the code a bit, which has been done in c28aac0 and c448834. More here: #134330 (comment)
libcxx/include/__tree
Outdated
__lhs.second = std::forward<_From>(__rhs).second; | ||
#if _LIBCPP_STD_VER >= 26 | ||
|
||
if constexpr (integral_constant<bool, is_copy_constructible<decltype(__rhs.first)>::value >()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto, if constexpr (is_copy_constructible_v<decltype(__rhs.first)>)
seems OK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simplification done here: 2661937
TEST_IGNORE_NODISCARD m.at(6); | ||
assert(false); | ||
} catch (std::out_of_range&) { | ||
# if TEST_STD_VER >= 26 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can use if TEST_STD_AT_LEAST_26_OR_RUNTIME_EVALUATED { /* ... */ }
. Ditto for all throwing cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if that macro can be used here, as the condition here appears to be TEST_STD_AT_LEAST_26_AND_RUNTIME_EVALUATED
And that macro always returns true
if TEST_STD_VER >= 26
Fixes #128660
Depends on:
libcxx/include/__memory/pointer_traits.h
#157304std::__tree_node
construction #153908Summary:
Apply
_LIBCPP_CONSTEXPR_SINCE_CXX26
tomap
,__tree
,node-handle
,__libcpp_erase_if
,__lazy_synth_three_way_comparator
,__lazy_compare_result
,libcxx/include/__utility/try_key_extraction.h
Skip test
erase_if.pass.cpp
for GCC during constant evaluation. AFAICT this appears to be a g++ bug, as the test passes with clangDisable test for
node-handle::key()
during constant evaluation (CWG2514). It is annotated with a// FIXME
map.modifiers/try.emplace.pass.cpp
: Start using the previously unusedmv3
. (Should this be a separate patch?)Has a TODO for
multimap
and ohtersa.
libcxx/test/std/containers/associative/map/map.ops/contains.pass.cpp
b.
libcxx/test/std/containers/associative/map/map.ops/contains_transparent.pass.cpp
a.
libcxx/test/std/containers/container.node/node_handle.pass.cpp
Fix typo in-> [NFC][libc++] Fix typo inlibcxx/include/__memory/pointer_traits.h
libcxx/include/__memory/pointer_traits.h
#157304pair<const MoveOnly, ...>
a.
move_assign.pass.cpp
b.
move_alloc.pass.cpp
c. Fails to compile if
static_assert(test());
is called in the test filed. Has a
// FIXME
with commented codeChangeThis became -> [libc++] Remove UB from__tree_node
to construct it's__node_value_type
during construction to avoid the:note: member call on object outside its lifetime is not allowed in a constant expression
issue.std::__tree_node
construction #153908